﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Linq;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Xml;
using FishServe.Build.Common;

namespace FishServe.Build.Module
{
    class Program
    {
        private const string _toolName = "FishServe.Build.Module.exe";
        private const int _expectedArgCount = 9;

        static int Main(string[] args)
        {
            return new Program().Start(args);
        }

        private int Start(string[] args)
        {
            // args = new string[] { "Debug", "FishServe", @"C:\2008-12-31\CEEDT\FishServe\.", @"FishServe.Common.Services", @"C:\2008-12-31\CEEDT\FishServe\FishServe.Common.Services\.", @"FishServe.Common.Services.csproj", @"FishServe.Common.Services", @"FishServe.Common.Services.dll", @"C:\2008-12-31\CEEDT\FishServe\FishServe.Common.Services\bin\Debug\." };
            // args = new string[] { "Debug", "FishServe", @"C:\2008-12-31\CEEDT\FishServe\.", @"FishServe.Client.Application", @"C:\2008-12-31\CEEDT\FishServe\FishServe.Client.Application\.", @"FishServe.Client.Application.csproj", @"FishServe.Client.Application", @"FishServe.Client.Application.dll", @"C:\2008-12-31\CEEDT\FishServe\FishServe.Client.Application\bin\Debug\." };
            // args = new string[] { "Debug", "FishServe", @"C:\2008-12-31\CEEDT\\FishServe\.", @"Microsoft.Practices.Unity", @"C:\2008-12-31\CEEDT\FishServe\Microsoft.Practices.Unity\.", @"Microsoft.Practices.Unity.csproj", @"Microsoft.Practices.Unity", @"Microsoft.Practices.Unity.dll", @"C:\2008-12-31\CEEDT\FishServe\Microsoft.Practices.Unity\bin\Debug\." };
            // args = new string[] { "Debug", "FishServe", @"C:\2008-12-31\CEEDT\FishServe\.", @"FishServe.Core", @"C:\2008-12-31\CEEDT\FishServe\FishServe.Core\.", @"FishServe.Core.csproj", @"FishServe.Core", "FishServe.Core.dll", @"C:\2008-12-31\CEEDT\FishServe\FishServe.Core\bin\Debug\." };
            // args = new string[] { "Debug", "FishServe", @"E:\2008-12-31\CEEDT\FishServe\.", @"FishServe.Client.Application" ,@"E:\2008-12-31\CEEDT\FishServe\FishServe.Client.Application\.", @"FishServe.Client.Application.csproj", "FishServe.Client.Application", "FishServe.Client.Application.dll", @"E:\2008-12-31\CEEDT\FishServe\FishServe.Client.Application\bin\Debug\." };

            WriteInfoLine("Started");
            try
            {
                if (PublishModule(args))
                {
                    WriteInfoLine("Exited (completed Ok)");
                    return Constants.ERRORLEVEL_SUCCESS;
                }
                else
                {
                    WriteErrorLine("Exited (failed)");
                    return Constants.ERRORLEVEL_FAIL_1;
                }
            }
            catch (Exception ex)
            {
                string exceptionString = "";
                Exception exception = ex;
                while (exception != null)
                {
                    exceptionString += String.Format("{0}\r\n{1}", ex.Message, ex.StackTrace);
                    exception = exception.InnerException;
                }

                WriteErrorLine(String.Format("Exception: {0}", exceptionString));
                WriteErrorLine("Exited (failed)");
                return Constants.ERRORLEVEL_FAIL_1;
            }
        }

        private bool PublishModule(string[] args)
        {
            if (args.Length != _expectedArgCount)
            {
                WriteErrorLine(String.Format("Incorrect number of arguments (expecting {0}, received {1})", _expectedArgCount, args.Length));
                
                for (int i = 0; i < args.Length; i++)
                    WriteInfoLine(String.Format("Args[{0}] = '{1}'", i, args[i]));

                return false;
            }

            string configurationName = args[0];
            string solutionName = args[1];
            string solutionDir = args[2].TrimEnd(new char[] { '.' });
            string projectName = args[3];
            string projectDir = args[4].TrimEnd(new char[] { '.' });
            string projectFileName = args[5];
            string targetName = args[6];
            string targetFilename = args[7];
            string targetDir = args[8].TrimEnd(new char[] { '.' });

            WriteDebugLine(String.Format("ConfigurationName='{0}'", configurationName));
            WriteDebugLine(String.Format("SolutionName='{0}'", solutionName));
            WriteDebugLine(String.Format("SolutionDir='{0}'", solutionDir));
            WriteDebugLine(String.Format("ProjectName='{0}'", projectName));
            WriteDebugLine(String.Format("ProjectDir='{0}'", projectDir));
            WriteDebugLine(String.Format("ProjectFileName='{0}'", projectFileName));
            WriteDebugLine(String.Format("TargetName='{0}'", targetName));
            WriteDebugLine(String.Format("TargetFilename='{0}'", targetFilename));
            WriteDebugLine(String.Format("TargetDir='{0}'", targetDir));

            if (projectName != targetName )
            {
                WriteErrorLine(String.Format("Project assembly name '{0}' must be the same as the project name '{1}' (Project Properties | Application)", targetName, projectName));
                throw new Exception("Project setup error");
            }

            DirectoryInfo projectDirInfo = new DirectoryInfo( projectDir );
            if (projectDirInfo.Name != projectName)
            {
                WriteErrorLine(String.Format( "Project directory '{0}' must be the same as the project name '{1}'", projectDirInfo.Name, projectName));
                throw new Exception("Project setup error");
            }

            DirectoryInfo projectParentDirInfo = new DirectoryInfo( projectDirInfo.Parent.FullName.TrimEnd( new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar } ) );
            DirectoryInfo solutionDirInfo = new DirectoryInfo( solutionDir.TrimEnd( new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar } ) );
            if (projectParentDirInfo.FullName != solutionDirInfo.FullName)
            {
                WriteErrorLine(String.Format("Project '{0}' must exist within solution folder '{1}'.", projectDirInfo.Name, solutionDirInfo.FullName));
                throw new Exception("Project setup error");
            }

            if ((projectName + ".csproj") != projectFileName)
            {
                FileInfo projectFilenameInfo = new FileInfo(projectFileName);
                string projectFilenameWithoutExt = projectFilenameInfo.Name.Substring(0, (projectFilenameInfo.Name.Length - projectFilenameInfo.Extension.Length));
                WriteErrorLine(String.Format("Project file [{1}].csproj must have the same name as the project name '{1}'.", projectFilenameWithoutExt, projectName));
                throw new Exception("Project setup error");
            }

            Solution solutionConfig = LoadSolutionConfig(solutionDir, solutionName);
            Project projectConfig = LoadProjectConfig(projectName, projectDir, projectFileName);
            //if (projectConfig.RunPostBuildEvent != ProjectRunPostBuildEvent.OnOutputUpdated)
            //{
            //    WriteWarningLine(String.Format("RunPostBuildEvent '{0}'", targetName ));
            //    throw new Exception("RunPostBuildEvent warning");
            //}

            string solutionPublishConfigFullFilename = Path.GetFullPath(Path.Combine(Path.Combine(solutionDir, "Solution Items"), Constants.SolutionBuildConfigFilename));

            if (!File.Exists(Path.GetFullPath(solutionPublishConfigFullFilename)))
            {
                WriteErrorLine(String.Format("Solution publish config file not found ({0})", solutionPublishConfigFullFilename));
                return false;
            }

            WriteInfoLine(String.Format("Using config file {0}", solutionPublishConfigFullFilename));

            XDocument solutionPublishConfigDoc = LoadSolutionPublishConfig(solutionPublishConfigFullFilename);
			//Console.WriteLine(solutionPublishConfigDoc.ToString());

            string deploymentDir = solutionPublishConfigDoc.Element("PublishConfig").Attribute("DeploymentDir").Value;
            
            // TODO: replace ModuleBinder with AssemblyResolver used in bootstrap
            AssemblyResolver.Initialise(deploymentDir,Constants.ModulesFolderName);

            Debug.Assert(!String.IsNullOrEmpty(deploymentDir), "DeploymentDir value missing " + solutionPublishConfigFullFilename);

            CreateDirectory(deploymentDir);
            CreateDirectory(Path.Combine(deploymentDir, Constants.ModulesFolderName));
            
            XDocument modulePublishConfigDoc = LoadModulePublishConfig(projectName, projectDir);

            string moduleTypeString = modulePublishConfigDoc.Element("PublishConfig").Attribute("ModuleType").Value;

            Debug.Assert(!String.IsNullOrEmpty(moduleTypeString), String.Format("ModuleType attribute missing from {0} ({1}) (expecting: 'bootstrap' | 'application' | 'shell' | 'addin' | 'module' | 'content')", Constants.ModulePublishConfigFilename, projectName));

            ModuleType moduleType;
            try
            {
                moduleType = (ModuleType)Enum.Parse(typeof(ModuleType), moduleTypeString);
            }
            catch(Exception)
            {
                WriteErrorLine(String.Format("Unrecognised ModuleType '{0}' in {1} ({2})(expecting: 'bootstrap' | 'application' |'shell' | 'addin' | 'module' | 'content')", moduleTypeString, Constants.ModulePublishConfigFilename, projectName));
                throw;
            }

            if (moduleType != ModuleType.Bootstrap && moduleType != ModuleType.Content && projectConfig.OutputType != ProjectOutputType.ClassLibrary)
            {
                WriteErrorLine(String.Format("Module '{0}' project 'Output Type' must be to 'Class Library'.  Application, Shell, AddIn and Module type modules must be a class library.", targetName));
                throw new Exception("Invalid project output type");
            }

            Assembly assembly = null;
            
            if( moduleType != ModuleType.Content )
                assembly = LoadAssembly(targetDir, targetFilename, true);

            switch (moduleType)
            {
                case ModuleType.Bootstrap :
                    DeployBootstrapModule(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);
                    break;
                case ModuleType.Application:
                    DeployApplicationModule(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);
                    break;
                case ModuleType.Shell :
                    DeployShellModule(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);
                    break;
                case ModuleType.AddIn :
                    DeployModule(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);
                    break;
                case ModuleType.Module :
                    DeployModule(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);
                    break;
                case ModuleType.Content :
                    DeployModule(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);
                    break;
            }

            return true;
        }

        private Project LoadProjectConfig( string projectName, string projectDir, string projectFilename )
        {
            string projectFullFilename = Path.Combine(projectDir, projectFilename);
            if (!File.Exists(projectFullFilename))
            {
                WriteErrorLine(String.Format("Project file not found: {0}", projectFullFilename));
                throw new Exception("Project file not found");
            }
            return Project.Load(projectFullFilename);
        }

        private XDocument LoadSolutionPublishConfig(string configFullFilename)
        {
            try
            {
                return XDocument.Load(configFullFilename);
            }
            catch (Exception)
            {
                WriteErrorLine( String.Format( "Error loading solution publish configuration {0}", configFullFilename ));
                throw;
            }
        }

        private XDocument LoadModulePublishConfig(string projectName, string projectDir)
        {
            string modulePublishConfigFullFilename = Path.Combine(projectDir, Constants.ModulePublishConfigFilename);
            if (!File.Exists(modulePublishConfigFullFilename))
            {
                WriteErrorLine(String.Format("{0} missing in project '{1}'", Constants.ModulePublishConfigFilename, projectName));
                throw new FileNotFoundException(String.Format("{0} not found in project '{1}'", Constants.ModulePublishConfigFilename, projectDir));
            }

            XDocument modulePublishConfigDoc = null;
            try
            {
                modulePublishConfigDoc = XDocument.Load(modulePublishConfigFullFilename);
            }
            catch (Exception)
            {
                WriteErrorLine(String.Format("Error loading {0} for project {1}", Constants.ModulePublishConfigFilename, projectName));
                throw;
            }
            return modulePublishConfigDoc;
        }

        private void DeployBootstrapModule(ModuleType moduleType, 
                                           string configurationName,
                                           string solutionName,
                                           string solutionDir,
                                           string projectName,
                                           string projectDir,
                                           string targetName,
                                           string targetFilename,
                                           string targetDir,
                                           string deploymentDir,
                                           Solution solutionConfig,
                                           Project projectConfig,
                                           XDocument solutionPublishConfigDoc,
                                           XDocument modulePublishConfigDoc,
                                           Assembly assembly)
        {
            DeployModule(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);

            if (Path.GetDirectoryName(targetDir).ToLower() != Path.GetDirectoryName(deploymentDir).ToLower())
            {
               WriteWarningLine(String.Format("Project output path for bootstrap project '{0}' should be set to '{1}' for debugging.  Set Output Path on Build tab of Project Properties.", projectName, Path.GetFullPath(deploymentDir)));

               string targetFullFilename = Path.Combine(targetDir, targetFilename);
               string moduleFullFilename = Path.Combine(deploymentDir, targetFilename);

               try
               {
                   File.Copy(targetFullFilename, moduleFullFilename, true);
                   WriteInfoLine(String.Format("Copied bootstrap '{0}' from '{1}' to '{2}'", targetFilename, targetDir, deploymentDir));
               }
               catch (Exception)
               {
                   WriteErrorLine(String.Format("Failed to copy bootstrap {0} from '{1}' to '{2}'", targetFilename, targetDir, deploymentDir));
                   throw;
               }
            }
        }

        private void DeployApplicationModule(ModuleType moduleType,
                                             string configurationName,
                                             string solutionName,
                                             string solutionDir,
                                             string projectName,
                                             string projectDir,
                                             string targetName,
                                             string targetFilename,
                                             string targetDir,
                                             string deploymentDir,
                                             Solution solutionConfig,
                                             Project projectConfig,
                                             XDocument solutionPublishConfigDoc,
                                             XDocument modulePublishConfigDoc,
                                             Assembly assembly)
        {
            DeployModule(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);

            //AssemblyName assemblyName = assembly.GetName();

            //string manifestSourceFilename = String.Format("{0}.manifest", targetName);
            //string manifestSourceDir = String.Format("{0}\\{1}\\{2}\\{3}\\", deploymentDir, _modulesFolderName, targetName, assemblyName.Version.ToString());
            //string manifestDestinationFilename = String.Format("{0}.{1}.{2}.manifest", targetName, assemblyName.Version.Major, assemblyName.Version.Minor);
            //string manifestDestinationDir = deploymentDir;
            //string manifestSourceFullFilename = Path.Combine(manifestSourceDir, manifestSourceFilename);
            //string manifestDestinationFullFilename = Path.Combine(manifestDestinationDir, manifestDestinationFilename);

            //try
            //{
            //    File.Copy(manifestSourceFullFilename, manifestDestinationFullFilename, true);
            //    WriteInformationLine(String.Format("Copied application manifest '{0}' to '{1}'", manifestSourceFullFilename, manifestDestinationFullFilename));
            //}
            //catch (Exception)
            //{
            //    WriteErrorLine(String.Format("Failed to copy application manifest '{0}' to '{1}'", manifestSourceFullFilename, manifestDestinationFullFilename));
            //    throw;
            //}
        }

        private void DeployShellModule( ModuleType moduleType,
                                        string configurationName,
                                        string solutionName,
                                        string solutionDir,
                                        string projectName,
                                        string projectDir,
                                        string targetName,
                                        string targetFilename,
                                        string targetDir,
                                        string deploymentDir,
                                        Solution solutionConfig,
                                        Project projectConfig,
                                        XDocument solutionPublishConfigDoc,
                                        XDocument modulePublishConfigDoc,
                                        Assembly assembly)
        {
            DeployModule(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);
        }

        private void DeployModule( ModuleType moduleType,
                                   string configurationName,
                                   string solutionName,
                                   string solutionDir,
                                   string projectName,
                                   string projectDir,
                                   string targetName,
                                   string targetFilename,
                                   string targetDir,
                                   string deploymentDir,
                                   Solution solutionConfig,
                                   Project projectConfig,
                                   XDocument solutionPublishConfigDoc,
                                   XDocument modulePublishConfigDoc,
                                   Assembly assembly)
        {
            string moduleDir = Path.Combine(Path.Combine(deploymentDir, Constants.ModulesFolderName), targetName);
            CreateDirectory(moduleDir);

            string moduleVersion ="";
            string moduleVersionDir = "";
            string moduleGuid = "";

            if (moduleType == ModuleType.Content)
            {
                XAttribute attribute = modulePublishConfigDoc.Element("PublishConfig").Attribute("Version");
                if (attribute == null || String.IsNullOrEmpty(attribute.Value))
                {
                    WriteErrorLine(String.Format("Missing Version attribute in '{0}' for module '{1}'.  Content modules require a Version attribute to be declared in the PublishConfig element of {0}.", Constants.ModulePublishConfigFilename, targetName));
                    throw new Exception( "Version attribute missing" );
                }
                moduleVersion = attribute.Value;
                moduleVersionDir = Path.Combine(moduleDir, moduleVersion);
                CreateDirectory(moduleVersionDir);
                ClearDirectory(moduleVersionDir);

                XAttribute moduleGuidAttribute = modulePublishConfigDoc.Element("PublishConfig").Attribute("ModuleGuid");
                if (moduleGuidAttribute == null || String.IsNullOrEmpty(moduleGuidAttribute.Value))
                {
                    WriteErrorLine(String.Format("Missing ModuleGuid attribute in '{0}' for module '{1}'.  Content modules require a ModuleGuid attribute to be declared in the PublishConfig element of {0}.", Constants.ModulePublishConfigFilename, targetName));
                    throw new Exception("ModuleGuid attribute missing");
                }
                moduleGuid = moduleGuidAttribute.Value;
            }
            else
            {
                AssemblyName assemblyName = assembly.GetName();

                moduleGuid = GetAssemblyGuid(assembly, true);
                string publicKeyToken = GetAssemblyPublicKeyToken(assembly, true);

                // The default AssemblyVersion attribute value in AssemblyInfo.cs is '1.0.0.0' (Major.Minor.[Build.[Revision]])  
                // To ensure each build has a unique version, AssemblyVersion should initially be set to '1.0.*',
                // then Major and Minor version numbers manually updated as required.
                // The following attempts to detect if the default AssemblyVersion setting is still present.
                // When '1.0.*' is used, Version.Build is set to the number of days since January 1 2000,  
                // and Version.Revision is set to the number of seconds since midnight/2.
                // TODO: Alternatively, inspect AssemblyInfo.cs directly.
                if (assemblyName.Version.Build < 3000)
                {
                    string msg = String.Format("Invalid assembly version '{0}' found in module '{1}'.  The AssemblyVersion attribute in AssemblyInfo.cs should initially be set to '1.0.*'.", assemblyName.Version.ToString(), targetName);
                    WriteErrorLine(msg);
                    throw new Exception(msg);
                }

                moduleVersion = assemblyName.Version.ToString();
                moduleVersionDir = Path.Combine(moduleDir, moduleVersion);
                CreateDirectory(moduleVersionDir);
                ClearDirectory(moduleVersionDir);

                string targetFullFilename = Path.Combine(targetDir, targetFilename);
                string moduleFullFilename = Path.Combine(moduleVersionDir, targetFilename);

                try
                {
                    File.Copy(targetFullFilename, moduleFullFilename);
                    WriteInfoLine(String.Format("Copied assembly '{0}' from '{1}' to '{2}'", targetFilename, targetDir, moduleVersionDir));
                }
                catch (Exception)
                {
                    WriteErrorLine(String.Format("Failed to copy assembly {0} from '{1}' to '{2}'", targetFilename, targetDir, moduleVersionDir));
                    throw;
                }
            }

            if( moduleType != ModuleType.Bootstrap) 
            {
                XDocument manifestDoc = null;

                switch (moduleType)
                {
                    case ModuleType.Application :
                        manifestDoc = CreateApplicationManifestDocument(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);
                        break;
                    case ModuleType.Shell :
                        manifestDoc = CreateShellManifestDocument(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);
                        break;
                    case ModuleType.AddIn :
                    case ModuleType.Module :
                        manifestDoc = CreateBaseManifestDocument(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);
                        break;
                    case ModuleType.Content :
                        manifestDoc = CreateContentManifestDocument(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, moduleVersion, moduleGuid);
                        break;
                }

                XAttribute attribute = null;

                attribute = modulePublishConfigDoc.Element("PublishConfig").Attribute("Category");
                if (attribute != null && !String.IsNullOrEmpty(attribute.Value))
                    manifestDoc.Element("ModuleManifest").Element("Module").Add(new XAttribute("Category", attribute.Value));

                attribute = modulePublishConfigDoc.Element("PublishConfig").Attribute("Description");
                if (attribute != null && !String.IsNullOrEmpty(attribute.Value))
                    manifestDoc.Element("ModuleManifest").Element("Module").Add(new XAttribute("Description", attribute.Value));
                
                DeployContentFiles(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, manifestDoc, moduleVersion);

                XDocument signedManifestDoc = SignManifest(manifestDoc);

                string manifestFilename = String.Format("{0}.manifest", targetName);
                string manifestFullFilename = Path.Combine(moduleVersionDir, manifestFilename);

                if (File.Exists(manifestFullFilename))
                    File.Delete(manifestFullFilename);

                signedManifestDoc.Save(manifestFullFilename);

                WriteInfoLine(String.Format("Created {0} manifest {1}", moduleType.ToString().ToLower(), manifestFullFilename));
            }
        }

        private void DeployContentFiles(ModuleType moduleType,
                                        string configurationName,
                                        string solutionName,
                                        string solutionDir,
                                        string projectName,
                                        string projectDir,
                                        string targetName,
                                        string targetFilename,
                                        string targetDir,
                                        string deploymentDir,
                                        Solution solutionConfig,
                                        Project projectConfig,
                                        XDocument solutionPublishConfigDoc,
                                        XDocument modulePublishConfigDoc,
                                        XDocument manifestDoc,
                                        string moduleVersion)
        {
            XElement contentFilesElement = null;
            string hashAlgorithm = null;

            foreach (ProjectContent projectContent in projectConfig.Contents)
            {
                if (projectContent.CopyType == ProjectContentCopyType.Always || projectContent.CopyType == ProjectContentCopyType.PreserveNewest)
                {
                    string sourceFilename = Path.Combine(projectDir, projectContent.IncludeFilename);
                    if (!File.Exists(sourceFilename))
                    {
                        WriteErrorLine( String.Format( "Missing module content file '{0}'.  Attempting to deployment content file for module '{1}' version '{2}'.", sourceFilename, targetName, moduleVersion ) );
                        throw new Exception( "Missing content file" );
                    }

                    string moduleVersionDir = Path.Combine(Path.Combine(Path.Combine(deploymentDir, Constants.ModulesFolderName), targetName), moduleVersion);
                    CreateDirectory(moduleVersionDir);

                    try
                    {
                        File.Copy(sourceFilename, Path.Combine(moduleVersionDir, projectContent.IncludeFilename));
                        WriteInfoLine( String.Format("Copied module content file '{0}' to '{1}'", sourceFilename, moduleVersionDir ) );
                    }
                    catch( Exception ex )
                    {
                        WriteErrorLine( String.Format("File copy failed.  Attempted to deploy module content file '{0}' to '{1}'", sourceFilename, moduleVersionDir ) );
                        throw new Exception( "File copy failed", ex );
                    }

                    if( contentFilesElement == null )
                    {
                        contentFilesElement = new XElement("Content");
                        manifestDoc.Element("ModuleManifest").Add( contentFilesElement );

                        XAttribute hashAlgorithmAttribute = solutionPublishConfigDoc.Element("PublishConfig").Attribute("FileHashAlgorithm");
                        if (hashAlgorithmAttribute == null || String.IsNullOrEmpty(hashAlgorithmAttribute.Value))
                        {
                            WriteErrorLine(String.Format("FileHashAlgorithm attribute missing from PublishConfig element in config file '{0}'.  Possible values include 'SHA1', 'SHA256', 'SHA384', 'SHA512', 'MD5'.", Constants.SolutionBuildConfigFilename));
                            throw new Exception("Missing FileHashAlgorithm attribute");
                        }
                        hashAlgorithm = hashAlgorithmAttribute.Value;
                    }

					string encodedHash = MyExtensions.GetFileHash(sourceFilename, hashAlgorithm);// GetFileHash(sourceFilename, hashAlgorithm);

                    contentFilesElement.Add(new XElement("IncludeFile",
                                                new XAttribute("Name", projectContent.IncludeFilename),
                                                new XAttribute("FileHash", encodedHash)
                                                )
                                            );

                }
            }
        }

       

        private static System.IO.FileStream GetFileStream(string pathName)
        {
            return (new System.IO.FileStream(pathName, System.IO.FileMode.Open,
                      System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite));
        }

        private XDocument CreateContentManifestDocument(ModuleType moduleType,
                                                        string configurationName,
                                                        string solutionName,
                                                        string solutionDir,
                                                        string projectName,
                                                        string projectDir,
                                                        string targetName,
                                                        string targetFilename,
                                                        string targetDir,
                                                        string deploymentDir,
                                                        Solution solutionConfig,
                                                        Project projectConfig,
                                                        XDocument solutionPublishConfigDoc,
                                                        XDocument modulePublishConfigDoc,
                                                        string moduleVersion,
                                                        string moduleGuid)
        {
            string manifestGuid = Guid.NewGuid().ToString().ToLower();

            XDocument manifestDoc = new XDocument(
                new XElement("ModuleManifest",
                    new XAttribute("ManifestType", moduleType.ToString()),
                    new XAttribute("Version", String.Format("{0}", moduleVersion)),
                    new XAttribute("Guid", manifestGuid),
                    new XAttribute("Created", DateTime.Now.ToString("yyyy-MM-dd HHHH:mm:ss")),
                    new XElement("Module",
                        new XAttribute("ModuleName", targetName),
                        new XAttribute("ModuleType", moduleType.ToString()),
                        new XAttribute("Version", moduleVersion),
                        new XAttribute("Guid", moduleGuid),
                        new XAttribute("ManifestGuid", manifestGuid)
                    )
                )
            );

            return manifestDoc;
        }

        private XDocument CreateBaseManifestDocument(ModuleType moduleType,
                                                     string configurationName,
                                                     string solutionName,
                                                     string solutionDir,
                                                     string projectName,
                                                     string projectDir,
                                                     string targetName,
                                                     string targetFilename,
                                                     string targetDir,
                                                     string deploymentDir,
                                                     Solution solutionConfig,
                                                     Project projectConfig,
                                                     XDocument solutionPublishConfigDoc,
                                                     XDocument modulePublishConfigDoc,
                                                     Assembly assembly)
        {
            string assemblyGuid = GetAssemblyGuid(assembly, true);
            string publicKeyToken = GetAssemblyPublicKeyToken(assembly, true);
			string fileHash = ComputeAssemblyFileHash(assembly, solutionPublishConfigDoc.Element("PublishConfig"));


            AssemblyName assemblyName = assembly.GetName();
            string manifestGuid = Guid.NewGuid().ToString().ToLower();
			/*
			Console.WriteLine("-- NOEL --");
			Console.WriteLine("solutionDir:" + solutionDir);
			Console.WriteLine("targetName:" + targetName);
			Console.WriteLine("targetName:" + targetName);
			Console.WriteLine("targetFilename:" + targetFilename);
			Console.WriteLine("targetDir:" + targetDir);
			Console.WriteLine("deploymentDir:" + deploymentDir);
			Console.WriteLine(String.Format("{0}:{1}", assembly.FullName, assembly.Location));
			Console.WriteLine("FileHash: " + MyExtensions.GetFileHash(assembly.Location, hashAlgorithm));
			Console.WriteLine("====----------====");
			 */

			//CC NH: Added FileComputeHash
			XDocument manifestDoc = new XDocument(
                new XElement("ModuleManifest",
                    new XAttribute("ManifestType", moduleType.ToString()),
                    new XAttribute("Version", String.Format("{0}", assemblyName.Version.ToString())),
                    new XAttribute("Guid", manifestGuid),
                    new XAttribute("Created", DateTime.Now.ToString("yyyy-MM-dd HHHH:mm:ss")),
                    new XElement("Module",
                        new XAttribute("ModuleName", targetName),
                        new XAttribute("ModuleType", moduleType.ToString()),
                        new XAttribute("Version", assemblyName.Version.ToString()),
                        new XAttribute("Guid", assemblyGuid),
                        new XAttribute("Culture", assemblyName.CultureInfo.Name),
                        new XAttribute("PublicKeyToken", publicKeyToken),
                        new XAttribute("ManifestGuid", manifestGuid),
						new XAttribute("FileHash", fileHash)
                    )
                )
            );

            XElement manifestDependentModulesElement=null;
            AssemblyName[] referencedAssemblies = assembly.GetReferencedAssemblies();
            // Note: referenced assemblies that are added to a project (via Add Reference) but not actually used, are
            // not included in the assembly manifest.
            foreach( AssemblyName referencedAssembly in referencedAssemblies )
            {
                // TODO: the assumption being made here is that the referenced module has been compiled and published
                // to the deployment folder first.  Visual Studio takes care of such dependencies and should be ok assuming the 
                // post build event completes.  Is more required here to ensure that the correct version of the referenced module 
                // identified?

                string moduleDir = Path.Combine(Path.Combine(deploymentDir, Constants.ModulesFolderName), referencedAssembly.Name);
                if( Directory.Exists( moduleDir ) )
                {
                    string moduleVersionDir = Path.Combine( moduleDir, referencedAssembly.Version.ToString() );
                    if( !Directory.Exists( moduleVersionDir ) )
                    {
                        WriteErrorLine(String.Format("Failed to generate manifest for '{0}' (v{1}).  Unable to locate version directory '{2}' for dependent module '{3}' in modules folder '{4}'", targetName, assemblyName.Version.ToString(), referencedAssembly.Version.ToString(), referencedAssembly.Name, Path.Combine(deploymentDir, Constants.ModulesFolderName)));
                        throw new Exception( "Directory not found");
                    }

                    string referencedAssemblyManifestFullFilename = Path.Combine( moduleVersionDir, referencedAssembly.Name + ".manifest" );

                    XDocument referencedModuleSignedManifestDoc = XDocument.Load( referencedAssemblyManifestFullFilename );
                    XElement encodedManifestElement = referencedModuleSignedManifestDoc.Element("SignedManifest").Element("EncodedManifest");

                    byte[] manifestBytes = Convert.FromBase64String(encodedManifestElement.Value);
                    string manifestString = UTF8Encoding.UTF8.GetString(manifestBytes);

                    XDocument referencedModuleManifestDoc = XDocument.Parse(manifestString);
                    string referencedManifestGuid = referencedModuleManifestDoc.Element("ModuleManifest").Attribute("Guid").Value;
                    string referencedModuleGuid = referencedModuleManifestDoc.Element("ModuleManifest").Element("Module").Attribute("Guid").Value;
                    
                    // TODO: maybe cross-check ModuleGuid by linking projects using ProjectGuid defined in .csproj 
                    // (to compare referenced project AssemblyInfo.cs [assembly: Guid("xxx")] with ModuleGuid )
                    //
                    // e.g. From FishServe.Core.csproj:
                    // 
                    //  <?xml version="1.0" encoding="utf-8"?>
                    //  <Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
                    //    <PropertyGroup>
                    //      <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
                    //      <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
                    //      <ProductVersion>9.0.30729</ProductVersion>
                    //      <SchemaVersion>2.0</SchemaVersion>
                    //      <ProjectGuid>{33BBC8C3-EF3A-4BB6-A0AB-EE4C720B62D6}</ProjectGuid>
                    //
                    // From FishServe.Core.Services.csproj:
                    //  ...
                    //  <ItemGroup>
                    //     <ProjectReference Include="..\FishServe.Core\FishServe.Core.csproj">
                    //       <Project>{33BBC8C3-EF3A-4BB6-A0AB-EE4C720B62D6}</Project>
                    //       <Name>FishServe.Core</Name>
                    //     </ProjectReference>
                    //   </ItemGroup>
                    //

                    if( manifestDependentModulesElement == null )
                    {
                        manifestDependentModulesElement = new XElement("DependentModules");
                        manifestDoc.Root.Add(manifestDependentModulesElement);
                    }

                    XElement manifestDependentModuleElement = new XElement("Module", 
                                                                        new XAttribute("ModuleName", referencedAssembly.Name), 
                                                                        new XAttribute("Version", referencedAssembly.Version.ToString()), 
                                                                        new XAttribute("ModuleGuid", referencedModuleGuid),
                                                                        new XAttribute("ManifestGuid", referencedManifestGuid));

                    manifestDependentModulesElement.Add(manifestDependentModuleElement);
                }                
            }

            // Registered types
            XElement configRegisteredTypesElement = modulePublishConfigDoc.Element("PublishConfig").Element("RegisteredTypes");
            if (configRegisteredTypesElement != null)
            {
                XElement registeredTypesElement = null;
                foreach (XElement configRegisteredTypeElement in configRegisteredTypesElement.Elements("RegisteredType"))
                {
                    XAttribute configTypeNameAttribute = configRegisteredTypeElement.Attribute("ModuleName");
                    XAttribute configLifetimeAttribute = configRegisteredTypeElement.Attribute("Lifetime");

                    XElement fromTypeElement = configRegisteredTypeElement.Element("FromType");
                    if (fromTypeElement == null)
                    {
                        WriteErrorLine(String.Format("Missing FromType element in RegisteredType declaration ({0}).", Path.Combine(projectDir, Constants.ModulePublishConfigFilename)));
                        throw new Exception("Missing FromType in RegisteredType declaraion");
                    }

                    XElement toTypeElement = configRegisteredTypeElement.Element("ToType");
                    if (toTypeElement == null)
                    {
                        WriteErrorLine(String.Format("Missing ToType element in RegisteredType declaration ({0}).", Path.Combine(projectDir, Constants.ModulePublishConfigFilename)));
                        throw new Exception("Missing ToType in RegisteredType declaraion");
                    }

                    if (registeredTypesElement == null)
                    {
                        registeredTypesElement = new XElement("RegisteredTypes");
                        manifestDoc.Root.Add(registeredTypesElement);
                    }

                    XElement registeredTypeElement = new XElement("RegisteredType");
                    registeredTypesElement.Add(registeredTypeElement);
                    if (configTypeNameAttribute != null && !String.IsNullOrEmpty(configTypeNameAttribute.Value))
                        registeredTypeElement.Add(new XAttribute("RegisteredName", configTypeNameAttribute.Value));
                    if (configLifetimeAttribute != null && !String.IsNullOrEmpty(configLifetimeAttribute.Value))
                        registeredTypeElement.Add(new XAttribute("Lifetime", configLifetimeAttribute.Value));

                    XElement fromElement = CreateRegisteredTypeMemberElement(moduleType,
                                                                             configurationName,
                                                                             solutionName,
                                                                             solutionDir,
                                                                             projectName,
                                                                             projectDir,
                                                                             targetName,
                                                                             targetFilename,
                                                                             targetDir,
                                                                             deploymentDir,
                                                                             solutionConfig, 
                                                                             projectConfig, 
                                                                             solutionPublishConfigDoc,
                                                                             modulePublishConfigDoc,
                                                                             assembly,
                                                                             fromTypeElement,
                                                                             "From");
                    registeredTypeElement.Add(fromElement);

                    XElement toElement = CreateRegisteredTypeMemberElement( moduleType,
                                                                           configurationName,
                                                                           solutionName,
                                                                           solutionDir,
                                                                           projectName,
                                                                           projectDir,
                                                                           targetName,
                                                                           targetFilename,
                                                                           targetDir,
                                                                           deploymentDir,
                                                                           solutionConfig, 
                                                                           projectConfig, 
                                                                           solutionPublishConfigDoc,
                                                                           modulePublishConfigDoc,
                                                                           assembly,
                                                                           toTypeElement,
                                                                           "To");
                    registeredTypeElement.Add(toElement);
                }
            }

            // ChildModules
            XElement childModulesElement = modulePublishConfigDoc.Element("PublishConfig").Element("ChildModules");
            if (childModulesElement != null)
            {
                XElement manifestChildModulesElement = null;
                foreach (XElement childModuleElement in childModulesElement.Elements())
                {
                    if (childModuleElement.Name != "ChildModule")
                    {
                        WriteErrorLine(String.Format("Invalid element '{0}' in ChildModules section in module '{1}'.  Expecting 'ChildModule' element in '{2}'.", childModuleElement.Name, targetName, Path.Combine(projectDir, Constants.ModulePublishConfigFilename)));
                        throw new Exception("Invalid ChildModules element");
                    }

                    XAttribute childModuleNameAttribute = childModuleElement.Attribute("ModuleName");
                    if (childModuleNameAttribute == null || String.IsNullOrEmpty(childModuleNameAttribute.Value))
                    {
                        WriteErrorLine(String.Format("Missing ModuleName attribute in ChildModule declaration in '{0}'.", Path.Combine(projectDir, Constants.ModulePublishConfigFilename)));
                        throw new Exception("Missing Name attribute");
                    }

                    XAttribute childModuleCategoryAttribute = childModuleElement.Attribute("Category");
                    XAttribute childModuleDescriptionAttribute = childModuleElement.Attribute("Description");
                    
                    string childModuleName = childModuleNameAttribute.Value;
                    string childModuleProjectDir = Path.Combine(solutionDir, childModuleName);
                    if (!Directory.Exists(childModuleProjectDir))
                    {
                        WriteErrorLine(String.Format("Project directory for child module '{0}' not found '{1}'.  Attempting to publish module '{2}'.  Check ChildModule declarations in '{3}'.", childModuleName, childModuleProjectDir, targetName, Path.Combine(projectDir, Constants.ModulePublishConfigFilename)));
                        throw new Exception("Invalid ChildModule declaration");
                    }

                    string childModuleProjectConfigFilename = Path.Combine(childModuleProjectDir, childModuleName + ".csproj");
                    if (!File.Exists(childModuleProjectConfigFilename))
                    {
                        WriteErrorLine(String.Format("Project file for child module '{0}' not found '{1}'.  Attempting to publish module '{2}'.", childModuleName, childModuleProjectConfigFilename, targetName));
                        throw new Exception("Project file not found");
                    }

                    if (!CheckProjectBuildDependency(solutionConfig, solutionName, solutionDir, childModuleName, targetName, projectConfig))
                    {
                        WriteErrorLine(String.Format("Project dependency required.  Module '{0}' has a runtime dependency on module '{1}' due to the ChildModule declaration in '{2}'.  To ensure these modules are compiled & published in the correct order, manually add the dependency using Solution Property Pages | Project Dependencies.", targetName, childModuleName, Path.Combine(projectDir, Constants.ModulePublishConfigFilename)));
                        throw new Exception("Project dependency required");
                    }

                    ModuleInfo moduleInfo = GetLatestModuleVersionInfo(deploymentDir, childModuleName, targetName);

                    if (manifestChildModulesElement == null)
                    {
                        manifestChildModulesElement = new XElement("ChildModules");
                        manifestDoc.Root.Add(manifestChildModulesElement);
                    }

                    XElement manifestChildModuleElement = new XElement("ChildModule",
                                                                new XAttribute("ModuleName", childModuleName),
                                                                new XAttribute("ModuleType", moduleInfo.ModuleType.ToString()),
                                                                new XAttribute("Version", moduleInfo.ModuleVersion),
                                                                new XAttribute("ModuleGuid", moduleInfo.ModuleGuid),
                                                                new XAttribute("ModuleManifestGuid", moduleInfo.ModuleManifestGuid)
                                                                );

                    // Module Category is a user defined field that can be used when selecting a child module to load.
                    // For example, Catch Effort form modules will be declared as child modules within the CatchEffort add-in
                    // module (in PublishConfig.xml). Category for all catch effort form modules could be set to 'CatchEffortForm'
                    // to enable the catch effort add-in to distinguish them from other child modules.
                    // Catgory values can be defined in two places.  All modules can defined a Category attribute in 
                    // <PublishConfig>.  An overriding Category value can also be defined in a
                    // <ChildModule> declaration.  The same applies for Description.
                    string childCategory = String.Empty;
                    if (childModuleCategoryAttribute != null && !String.IsNullOrEmpty(childModuleCategoryAttribute.Value))
                        childCategory = childModuleCategoryAttribute.Value;
                    else if (!String.IsNullOrEmpty(moduleInfo.ModuleCategory))
                        childCategory = moduleInfo.ModuleCategory;
                    if (childCategory != String.Empty)
                        manifestChildModuleElement.Add(new XAttribute("Category", childCategory));

                    string childDescription = String.Empty;
                    if (childModuleDescriptionAttribute != null && !String.IsNullOrEmpty(childModuleDescriptionAttribute.Value))
                        childDescription = childModuleDescriptionAttribute.Value;
                    else if (!String.IsNullOrEmpty(moduleInfo.ModuleDescription))
                        childDescription = moduleInfo.ModuleDescription;
                    if (childDescription != String.Empty)
                        manifestChildModuleElement.Add(new XAttribute("Description", childDescription));

                    manifestChildModulesElement.Add(manifestChildModuleElement);
                    
                    //attribute = modulePublishConfigDoc.Element("PublishConfig").Attribute("Category");
                    //if (attribute != null && !String.IsNullOrEmpty(attribute.Value))
                    //    manifestDoc.Element("ModuleManifest").Element("Module").Add(new XAttribute("Category", attribute.Value));

                    //attribute = modulePublishConfigDoc.Element("PublishConfig").Attribute("Description");
                    //if (attribute != null && !String.IsNullOrEmpty(attribute.Value))
                    //    manifestDoc.Element("ModuleManifest").Element("Module").Add(new XAttribute("Description", attribute.Value));
                }
            }

            return manifestDoc;
        }

        private XElement CreateRegisteredTypeMemberElement(ModuleType moduleType,
                                                           string configurationName,
                                                           string solutionName,
                                                           string solutionDir,
                                                           string projectName,
                                                           string projectDir,
                                                           string targetName,
                                                           string targetFilename,
                                                           string targetDir,
                                                           string deploymentDir,
                                                           Solution solutionConfig,
                                                           Project projectConfig,
                                                           XDocument solutionPublishConfigDoc,
                                                           XDocument modulePublishConfigDoc,
                                                           Assembly assembly,
                                                           XElement configMemberElement,
                                                           string memberCategory)
        {
            XAttribute memberTypeAttribute = configMemberElement.Attribute("TypeName");
            if (memberTypeAttribute == null || (memberTypeAttribute != null && String.IsNullOrEmpty(memberTypeAttribute.Value)))
            {
                WriteErrorLine(String.Format("Missing Type attribute in {0}Type declaration ({1}).", memberCategory, Path.Combine(projectDir, Constants.ModulePublishConfigFilename)));
                throw new Exception("Missing Type attribute");
            }
            string memberTypeName = memberTypeAttribute.Value;

            XAttribute memberModuleNameAttribute = configMemberElement.Attribute("ModuleName");
            if (memberModuleNameAttribute == null || (memberModuleNameAttribute != null && String.IsNullOrEmpty(memberModuleNameAttribute.Value)))
            {
                WriteErrorLine(String.Format("Missing ModuleName attribute in {0}Type declaration ({1}).", memberCategory, Path.Combine(projectDir, Constants.ModulePublishConfigFilename)));
                throw new Exception("Missing ModuleName attribute");
            }
            string memberModuleName = memberModuleNameAttribute.Value;

            string memberModuleDir = Path.Combine(Path.Combine(deploymentDir, Constants.ModulesFolderName), memberModuleName);
            if (!Directory.Exists(memberModuleDir))
            {
                WriteErrorLine(String.Format("Directory not found '{0}'.  Attempting to resolve latest version of registered type {1} type '{2}' in module '{3}' registered in '{4}' for module '{5}'", memberModuleDir, memberCategory.ToLower(), memberTypeName, memberModuleName, Constants.ModulePublishConfigFilename, assembly.GetName().Name));
                throw new Exception("Directory not found");
            }

            string[] versionDirectories = Directory.GetDirectories(memberModuleDir);
            if (versionDirectories.Length == 0)
            {
                WriteErrorLine(String.Format("Subdirectories not found '{0}'.  Attempting to resolve latest version of registered type {1} type '{2}' in module '{3}' registered in '{4}' for module '{5}'", memberModuleDir, memberCategory.ToLower(), memberTypeName, memberModuleName, Constants.ModulePublishConfigFilename, assembly.GetName().Name));
                throw new Exception("Subdirectories not found");
            }

            List<Version> versions = new List<Version>();
            foreach (string directory in versionDirectories)
            {
                string[] pathParts = directory.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
                Version version = ParseVersion(pathParts[pathParts.Length - 1]);
                if (version != null)
                    versions.Add(version);
            }

            if (versions.Count == 0)
            {
                WriteErrorLine(String.Format("Version subdirectories not found '{0}'.  Attempting to resolve latest version of registered type {1} type '{2}' in module '{3}' registered in '{4}' for module '{5}'", memberModuleDir, memberCategory.ToLower(), memberTypeName, memberModuleName, Constants.ModulePublishConfigFilename, assembly.GetName().Name));
                throw new Exception("Version subdirectories not found");
            }

            versions.Sort();

            string latestVersion = versions[versions.Count - 1].ToString();
            string moduleVersionDir = Path.Combine(memberModuleDir, latestVersion);

            string moduleAssemblyFullFilename = Path.Combine(moduleVersionDir, memberModuleName + ".dll");
            if (!File.Exists(moduleAssemblyFullFilename))
            {
                WriteErrorLine(String.Format("File not found '{0}'.  Attempting to resolve latest version of registered type {1} type '{2}' in module '{3}' registered in '{4}' for module '{5}'", moduleAssemblyFullFilename, memberCategory.ToLower(), memberTypeName, memberModuleName, Constants.ModulePublishConfigFilename, assembly.GetName().Name));
                throw new Exception("File not found");
            }

            // TODO: need to check that modules for registered types are being compiled/published before the registering module is compiled/published 
            if (!CheckProjectBuildDependency(solutionConfig, solutionName, solutionDir, memberModuleName, assembly.GetName().Name, projectConfig))
            {
                WriteErrorLine(String.Format("Project dependency required.  Project '{0}' has a runtime dependency on project '{1}' due to the RegisteredType declaration in '{2}' for '{3}'.  To ensure modules are compiled & published in the correct order, manually add the dependency using Solution Property Pages | Project Dependencies.", assembly.GetName().Name, memberModuleName, Constants.ModulePublishConfigFilename, memberTypeName));
                throw new Exception("Project dependency error");
            }

            Assembly memberAssembly = LoadAssembly(moduleVersionDir, memberModuleName + ".dll", true);
            Type memberType = memberAssembly.GetType(memberTypeName);

            if (memberType == null)
            {
                WriteErrorLine(String.Format("Type '{2}' not found in '{0}'.  Attempting to resolve latest version of registered type {1} type '{2}' in module '{3}' registered in '{4}' for module '{5}'", moduleAssemblyFullFilename, memberCategory.ToLower(), memberTypeName, memberModuleName, Constants.ModulePublishConfigFilename, assembly.GetName().Name));
                throw new Exception("File not found");
            }

            string assemblyVersion = memberAssembly.GetName().Version.ToString();
            if (assemblyVersion != latestVersion)
            {
                WriteErrorLine(String.Format("Invalid assembly version.  Assembly '{0}' in directory '{1}' is version '{2}' (expecting version '{3}')", memberModuleName + ".dll", moduleVersionDir, assemblyVersion, latestVersion));
                throw new Exception("Invalid assembly version");
            }

            string moduleManifestFullFilename = Path.Combine(moduleVersionDir, memberModuleName + ".manifest");
            XDocument moduleSignedManifestDoc = XDocument.Load(moduleManifestFullFilename);
            XElement encodedManifestElement = moduleSignedManifestDoc.Element("SignedManifest").Element("EncodedManifest");
            byte[] manifestBytes = Convert.FromBase64String(encodedManifestElement.Value);
            string manifestString = UTF8Encoding.UTF8.GetString(manifestBytes);
            XDocument moduleManifestDoc = XDocument.Parse(manifestString);
            string manifestVersion = moduleManifestDoc.Element("ModuleManifest").Attribute("Version").Value;
            if (manifestVersion != latestVersion)
            {
                WriteErrorLine(String.Format("Module/manifest version mismatch.  Manifest file '{0}' (v{1}) in directory '{4}' is not the correct version for assembly '{2}' (v{3}) in the same directory", memberModuleName + ".manifest", manifestVersion, memberModuleName + ".dll", latestVersion, moduleVersionDir));
                throw new Exception("Module/manifest mismatch");
            }

            string manifestGuid = moduleManifestDoc.Element("ModuleManifest").Attribute("Guid").Value;
            string manifestType = moduleManifestDoc.Element("ModuleManifest").Attribute("ManifestType").Value;

            XElement memberElement = new XElement(String.Format("{0}Type", memberCategory),
                                            new XAttribute("TypeName", memberTypeName),
                                            new XAttribute("ModuleName", memberModuleName),
                                            new XAttribute("ModuleType", manifestType),
                                            new XAttribute("ModuleVersion", latestVersion),
                                            new XAttribute("ModuleGuid", GetAssemblyGuid(memberAssembly, true)),
                                            new XAttribute("ModuleManifestGuid", manifestGuid));

            return memberElement;
        }

        private ModuleInfo GetLatestModuleVersionInfo(string deploymentDir,
                                                      string moduleName,
                                                      string publishModuleName)
        {
            ModuleInfo moduleInfo = null;

            string moduleDir = Path.Combine(Path.Combine(deploymentDir, Constants.ModulesFolderName), moduleName);
            if (!Directory.Exists(moduleDir))
            {
                WriteErrorLine(String.Format("Directory not found '{0}'.  Attempting to resolve latest version of module '{1}' to publish module '{2}'.", moduleDir, moduleName, publishModuleName ) );
                throw new Exception("Directory not found");
            }

            string[] versionDirectories = Directory.GetDirectories(moduleDir);
            if (versionDirectories.Length == 0)
            {
                WriteErrorLine(String.Format("Version directory(s) not found within '{0}'.  Attempting to resolve latest version of module '{1}' to publish module '{2}'.", moduleDir, moduleName, publishModuleName ) );
                throw new Exception("Version directory(s) not found");
            }

            List<Version> versions = new List<Version>();
            foreach (string directory in versionDirectories)
            {
                string[] pathParts = directory.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
                Version version = ParseVersion(pathParts[pathParts.Length - 1]);
                if (version != null)
                    versions.Add(version);
            }

            if (versions.Count == 0)
            {
                WriteErrorLine(String.Format("Version directories not found within '{0}'.  Attempting to resolve latest version of module '{1}' to publish module '{2}'.", moduleDir, moduleName, publishModuleName ) );
                throw new Exception("Version directories not found");
            }

            versions.Sort();

            string latestVersion = versions[versions.Count - 1].ToString();
            string moduleVersionDir = Path.Combine(moduleDir, latestVersion);

            string moduleAssemblyFullFilename = Path.Combine(moduleVersionDir, moduleName + ".dll");
            if (!File.Exists(moduleAssemblyFullFilename))
            {
                WriteErrorLine(String.Format("File not found '{0}'.  Attempting to resolve latest version of module '{1}' to publish module '{2}'.", moduleAssemblyFullFilename, moduleName, publishModuleName ) );
                throw new Exception("File not found");
            }

            Assembly moduleAssembly = LoadAssembly(moduleVersionDir, moduleName + ".dll", true);
            string assemblyVersion = moduleAssembly.GetName().Version.ToString();
            if (assemblyVersion != latestVersion)
            {
                WriteErrorLine(String.Format("Invalid assembly version.  Assembly '{0}' in directory '{1}' is version '{2}' (expecting version '{3}')", moduleName + ".dll", moduleVersionDir, assemblyVersion, latestVersion));
                throw new Exception("Invalid assembly version");
            }

            string moduleManifestFullFilename = Path.Combine(moduleVersionDir, moduleName + ".manifest");
            XDocument moduleSignedManifestDoc = XDocument.Load(moduleManifestFullFilename);
            XElement encodedManifestElement = moduleSignedManifestDoc.Element("SignedManifest").Element("EncodedManifest");
            byte[] manifestBytes = Convert.FromBase64String(encodedManifestElement.Value);
            string manifestString = UTF8Encoding.UTF8.GetString(manifestBytes);
            XDocument moduleManifestDoc = XDocument.Parse(manifestString);
            string manifestVersion = moduleManifestDoc.Element("ModuleManifest").Attribute("Version").Value;
            if (manifestVersion != latestVersion)
            {
                WriteErrorLine(String.Format("Module/manifest version mismatch.  Manifest file '{0}' (v{1}) in directory '{4}' is not the correct version for assembly '{2}' (v{3}) in the same directory", moduleName + ".manifest", manifestVersion, moduleName + ".dll", latestVersion, moduleVersionDir));
                throw new Exception("Module/manifest mismatch");
            }

            string manifestGuid = moduleManifestDoc.Element("ModuleManifest").Attribute("Guid").Value;

            moduleInfo = new ModuleInfo();
            moduleInfo.ModuleName = moduleName;
            moduleInfo.ModuleGuid = GetAssemblyGuid(moduleAssembly, true);
            moduleInfo.ModuleManifestGuid = manifestGuid;
            moduleInfo.ModuleVersion = latestVersion;
            try
            {
                moduleInfo.ModuleType = (ModuleType)Enum.Parse(typeof(ModuleType), moduleManifestDoc.Element("ModuleManifest").Attribute("ManifestType").Value);
            }
            catch (Exception)
            {
                WriteErrorLine(String.Format("Unrecognised module Type '{0}' declared in '{1}'", moduleManifestFullFilename));
                throw;
            }

            XAttribute attribute;
            moduleInfo.ModuleCategory = String.Empty;
            attribute = moduleManifestDoc.Element("ModuleManifest").Element("Module").Attribute("Category");
            if (attribute != null && !String.IsNullOrEmpty(attribute.Value))
                moduleInfo.ModuleCategory = attribute.Value;
                        
            moduleInfo.ModuleDescription = String.Empty;
            attribute = moduleManifestDoc.Element("ModuleManifest").Element("Module").Attribute("Description");
            if (attribute != null && !String.IsNullOrEmpty(attribute.Value))
                moduleInfo.ModuleDescription = attribute.Value;
                        
            return moduleInfo;
        }
                                                   

        private bool CheckProjectBuildDependency(Solution solutionConfig, string solutionName, string solutionDir, string parentProjectName, string childProjectName, Project projectConfig)
        {
            // Because many modules/assemblies will be loaded dynamically, VS is unable to 
            // detect project dependencies and therefore the build/publish order will likely be incorrect.  
            // To resolve this, project dependencies are manually defined via Solution Property Pages | Project Dependencies.
            // Here we check that the parent project is being compiled/published before the child project

            bool dependencyExists = false;

            // First check if there is a reference to the parent project in the child project (i.e. Add Reference)
            foreach (ProjectProjectReference projectReference in projectConfig.ProjectReferences)
            {
                if (projectReference.ProjectName == parentProjectName)
                {
                    dependencyExists = true;
                    break;
                }
            }

            if (!dependencyExists)
            {
                // Dependency may have been manually added via Solution Property Pages | Project Dependencies

                // Below is an extract from FishServe.sln.  A dependency was (manually) added to declare that
                // FishServe.Client.Shell depends on the FishServe.Common.Services (which will be loaded dynamically).
                //
                // Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FishServe.Client.Shell", "FishServe.Client.Shell\FishServe.Client.Shell.csproj", "{17083228-6B0C-4333-95A6-DE10D5A52F3F}"
                //      ProjectSection(ProjectDependencies) = postProject
                //          {C98548A9-F71F-4020-B5AD-5463807E9513} = {C98548A9-F71F-4020-B5AD-5463807E9513}
                //      EndProjectSection
                // EndProject
                //
                // Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FishServe.Common.Services", "FishServe.Common.Services\FishServe.Common.Services.csproj", "{C98548A9-F71F-4020-B5AD-5463807E9513}"
                // EndProject

                SolutionProject childProject = solutionConfig.FindProjectByName(childProjectName);
                if (childProject == null)
                {
                    WriteErrorLine(String.Format("Failed to find child project '{0}' in solution config file", childProjectName));
                    throw new Exception("Solution parse error");
                }

                SolutionProject parentProject = solutionConfig.FindProjectByName(parentProjectName);
                if (parentProject == null)
                {
                    WriteErrorLine(String.Format("Failed to find parent project '{0}' in solution config file", parentProjectName));
                    throw new Exception("Solution parse error");
                }

                foreach (SolutionProjectDependency projectDependency in childProject.ProjectDependencies)
                {
                    if (projectDependency.DependentProjectGuid == parentProject.ProjectGuid)
                    {
                        dependencyExists = true;
                        break;
                    }
                }
            }

            return dependencyExists;
        }

        private Solution LoadSolutionConfig(string solutionDir, string solutionName)
        {
            string solutionFilename = Path.Combine(solutionDir, solutionName + ".sln");
            if (!File.Exists(solutionFilename))
            {
                WriteErrorLine(String.Format("Solution file '{0}' not found", solutionFilename));
                throw new Exception("File not found");
            }

            Solution solution = new Solution();
            string[] lines = File.ReadAllLines(solutionFilename);
            bool withinProjectSection = false;
            bool withinProjectDependencySection = false;
            string[] tokens = null;
            string[] parts = null;
            SolutionProject project = null;
            SolutionProjectDependency projectDependency = null;
            foreach (string line in lines)
            {
                if (line.StartsWith("Project("))
                {
                    Trace.Assert(!withinProjectSection, "Solution file: 'Project(' but withinProjectSection=true");
                    withinProjectSection = true;

                    tokens = line.Substring(7).Split(new char[] { ',' });
                    if (tokens.Length != 3)
                    {
                        WriteErrorLine(String.Format("Solution: token count error, project line: '{0}'", line));
                        throw new Exception("Error parsing solution file");
                    }

                    parts = tokens[0].Split(new char[] { '=' });
                    if (parts.Length != 2)
                    {
                        WriteErrorLine(String.Format("Solution: parts count error, project line: '{0}', token[0] '{1}'", line, tokens[0]));
                        throw new Exception("Error parsing solution file");
                    }
                    string projectName = parts[1].TrimEnd(new char[] { '\t', ' ', '"' }).TrimStart(new char[] { '\t', ' ', '"' });
                    string projectGuid = tokens[2].TrimEnd(new char[] { '\t', ' ', '"' }).TrimStart(new char[] { '\t', ' ', '"' });

                    project = new SolutionProject(projectName, projectGuid);
                    solution.Projects.Add(project);
                }
                else if (line == "EndProject")
                {
                    Trace.Assert(withinProjectSection, "Solution file: 'ProjectEnd' but withinProjectSection=false");
                    withinProjectSection = false;
                    Trace.Assert(!withinProjectDependencySection, "Solution file: 'ProjectEnd' but withProjectDependencySection=true");
                }
                else if (line.Contains("ProjectSection(ProjectDependencies)"))
                {
                    Trace.Assert(withinProjectSection, "Solution file: 'ProjectSection(ProjectDependencies)' but withinProjectSection=false");
                    Trace.Assert(!withinProjectDependencySection, "Solution file: 'ProjectSection(ProjectDependencies)' but withinProjectDependencySection=true");
                    withinProjectDependencySection = true;
                }
                else if (line.Contains("EndProjectSection"))
                {
                    Trace.Assert(withinProjectSection, "Solution file: 'EndProjectSection' but withinProjectSection=false");
                    if (withinProjectDependencySection)
                        withinProjectDependencySection = false;
                }
                else if (withinProjectDependencySection)
                {
                    Trace.Assert(withinProjectSection, "Solution file: withinProjectDependencySection but withinProjectSection=false");
                    tokens = line.Split(new char[] { '=' });
                    if (tokens.Length != 2)
                    {
                        WriteErrorLine(String.Format("Solution: unhandled line in ProjectSection(ProjectDependencies) '{0}'", line));
                        throw new Exception("Error parsing solution file");
                    }

                    string dependentProjectGuid = tokens[0].TrimEnd(new char[] { '\t', ' ', '"' }).TrimStart(new char[] { '\t', ' ', '"' });

                    projectDependency = new SolutionProjectDependency(dependentProjectGuid);
                    project.ProjectDependencies.Add(projectDependency);
                }
            }
            return solution;
        }


        private Version ParseVersion(string versionString)
        {
            Version version = null;

            string[] versionParts = versionString.Split('.');
            if (versionParts.Length == 4)
            {
                foreach (string versionPart in versionParts)
                {
                    int outInt = 0;
                    if (!Int32.TryParse(versionPart, out outInt))
                        return null;
                }
                version = new Version(versionString);
            }
            return version;
        }

        private XDocument CreateApplicationManifestDocument(ModuleType moduleType,
                                                            string configurationName,
                                                            string solutionName,
                                                            string solutionDir,
                                                            string projectName,
                                                            string projectDir,
                                                            string targetName,
                                                            string targetFilename,
                                                            string targetDir,
                                                            string deploymentDir,
                                                            Solution solutionConfig,
                                                            Project projectConfig,
                                                            XDocument solutionPublishConfigDoc,
                                                            XDocument modulePublishConfigDoc,
                                                            Assembly assembly)
        {
            XDocument manifestDoc = CreateBaseManifestDocument(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);
            return manifestDoc;
        }

        private XDocument CreateShellManifestDocument(ModuleType moduleType,
                                                     string configurationName,
                                                     string solutionName,
                                                     string solutionDir,
                                                     string projectName,
                                                     string projectDir,
                                                     string targetName,
                                                     string targetFilename,
                                                     string targetDir,
                                                     string deploymentDir,
                                                     Solution solutionConfig,
                                                     Project projectConfig,
                                                     XDocument solutionPublishConfigDoc,
                                                     XDocument modulePublishConfigDoc,
                                                     Assembly assembly)
        {
            XDocument manifestDoc = CreateBaseManifestDocument(moduleType, configurationName, solutionName, solutionDir, projectName, projectDir, targetName, targetFilename, targetDir, deploymentDir, solutionConfig, projectConfig, solutionPublishConfigDoc, modulePublishConfigDoc, assembly);

            return manifestDoc;
        }

        private Assembly LoadAssembly(string assemblyPath, string assemblyFilename, bool throwNotFoundException)
        {
            string assemblyFullFilename = Path.Combine(assemblyPath, assemblyFilename);
            if (!File.Exists(assemblyFullFilename) && throwNotFoundException)
                throw new FileNotFoundException(String.Format("Assembly {0} not found ({1})", assemblyFilename, assemblyPath));

            try
            {
                return Assembly.LoadFrom(assemblyFullFilename);
               // return Assembly.ReflectionOnlyLoad(assemblyFullFilename);
            }
            catch (Exception)
            {
                WriteErrorLine(String.Format("Assembly.LoadFile() failed '{0}' ({1})", assemblyFilename, assemblyPath));
                throw;
            }
        }

        private string GetAssemblyGuid(Assembly assembly, bool throwNotFoundException)
        {
            object[] attributes = assembly.GetCustomAttributes(typeof(GuidAttribute), false);
            if (attributes.Length != 1)
            {
                if( throwNotFoundException )
                    throw new Exception(String.Format("Guid attribute missing from assembly {0}.", assembly.FullName));

                return String.Empty;
            }

            GuidAttribute guidAttribute = attributes[0] as GuidAttribute;

            return guidAttribute.Value.ToLower();
        }

        private string GetAssemblyPublicKeyToken(Assembly assembly, bool throwNotFoundException)
        {
            byte[] bytes = assembly.GetName().GetPublicKeyToken();
            if( bytes.Length == 0 && throwNotFoundException )
                throw new Exception(String.Format("Assembly must have a strong name.  Select 'Sign the assembly' on the Project Properties | Signing tab, and browse to {0} in 'Solution Items' folder ({1}).", Constants.CodeSigningPfxFilename, assembly.FullName));

            string publicKeyToken = "";
            for (int i = 0; i < bytes.GetLength(0); i++)
                publicKeyToken += String.Format("{0:x}", bytes[i]);

            return publicKeyToken.ToLower();
        }

        private XDocument SignManifest(XDocument manifestDoc)
        {
            byte[] manifestBytes = UTF8Encoding.UTF8.GetBytes(manifestDoc.ToString());

            string pfxFullFilename = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), Constants.CodeSigningPfxFilename);

            if( !File.Exists(pfxFullFilename) )
                throw new FileNotFoundException(String.Format("Cannot find PFX file '{0}' ({1})", Constants.CodeSigningPfxFilename, pfxFullFilename));

            X509Certificate2 certificatePfx=null;
            try
            {
                certificatePfx = new X509Certificate2(pfxFullFilename, Constants.CodeSigningPfxPassword);
            }
            catch (Exception)
            {
                WriteErrorLine(String.Format("Failed to load PFX file '{0}' ({1}):", Constants.CodeSigningPfxFilename, pfxFullFilename));
                throw;
            }

            RSACryptoServiceProvider privateKeyCsp = certificatePfx.PrivateKey as RSACryptoServiceProvider;
            byte[] signatureBytes = privateKeyCsp.SignData(manifestBytes, new SHA1CryptoServiceProvider());

            if (!privateKeyCsp.VerifyData(manifestBytes, new SHA1CryptoServiceProvider(), signatureBytes))
                throw new Exception("Manifest signature verification failed");

            string encodedManifest = Convert.ToBase64String(manifestBytes);
            string encodedSignature = Convert.ToBase64String(signatureBytes);

            XDocument signedManifestDoc = new XDocument(
                new XDeclaration("1.0", "utf-8", "yes"),
                new XComment("This file was created by a tool"),
                new XElement("SignedManifest",
                    new XElement("EncodedManifest", new XCData(encodedManifest)),
                    new XElement("EncodedSignature", new XCData(encodedSignature))
                ),
                new XText("\r\n\r\n"),
                new XComment( String.Format( " encoded manifest: \r\n\r\n{0}\r\n\r\n", manifestDoc.Root.ToString()) )
            );

            return signedManifestDoc;
        }

        private void CreateDirectory(string dir)
        {
            if (!Directory.Exists(dir))
            {
                try
                {
                    Directory.CreateDirectory(dir);
                    WriteInfoLine(String.Format("Created directory {0}", dir));
                }
                catch (Exception)
                {
                    WriteErrorLine(String.Format("Failed to create directory '{0}'", dir));
                    throw;
                }
            }
        }

        private void ClearDirectory(string dir)
        {
            string[] files = Directory.GetFiles(dir);
            foreach (string file in files)
            {
                try
                {
                    File.Delete(file);
                }
                catch (Exception)
                {
                    WriteErrorLine(String.Format("Failed to delete file {0}", file));
                    throw;
                }
            }
        }

        private void WriteErrorLine(string line)
        {
            Console.WriteLine(String.Format("{0}: Error:  {1}", _toolName, line));
        }

        private void WriteWarningLine(string line)
        {
            Console.WriteLine(String.Format("{0}: Warning: {1}", _toolName, line));
        }

        private void WriteInfoLine(string line)
        {
            Console.WriteLine(String.Format("{0}: {1}", _toolName, line));
        }

        private void WriteDebugLine(string line)
        {
            WriteInfoLine(line);
        }

		private string ComputeAssemblyFileHash(Assembly assembly, XElement solutionConfigBuild)
		{
			XAttribute xHashAlgorithm = solutionConfigBuild.Attribute("FileHashAlgorithm");
			DBC.AssertNotNull(xHashAlgorithm, "Expecting Element:PublishConfig with Attribute:FileHashAlogrithm in the Build.Config.xml");
			string hashAlgorithm = xHashAlgorithm.Value;

			return MyExtensions.GetFileHash(assembly.Location, hashAlgorithm);
		}

		private string ComputeAssemblyFileHash(string fullFilePath, XElement solutionConfigBuild)
		{
			XAttribute xHashAlgorithm = solutionConfigBuild.Attribute("FileHashAlgorithm");
			DBC.AssertNotNull(xHashAlgorithm, "Expecting Element:PublishConfig with Attribute:FileHashAlogrithm in the Build.Config.xml");
			string hashAlgorithm = xHashAlgorithm.Value;

			return MyExtensions.GetFileHash(fullFilePath, hashAlgorithm);
		}
    }
}
